package com.qijian.sync;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.util.TypeUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.qijian.common.annotation.KingDeeField;
import com.qijian.common.annotation.KingDeeTable;
import com.qijian.common.utils.StringUtils;
import com.qijian.common.utils.reflect.ReflectUtils;
import com.qijian.common.utils.sync.GetKingDeeData;
import com.qijian.common.utils.sync.KingDeeFile;
import com.qijian.common.utils.uuid.IdUtils;
import com.qijian.system.domain.SysConfig;
import com.qijian.system.service.ISysConfigService;
import com.qijian.system.service.IUserService;
import com.qijian.tool.domain.Storage;
import com.qijian.tool.service.IStorageService;
import com.qijian.tool.utils.HttpFileUtil;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.http.util.Asserts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import static com.qijian.common.utils.reflect.ReflectUtils.getAllField;

@Component
@NoArgsConstructor
public abstract class BaseKingDeeSync {

    protected static final String updateTimeFormat = "yyyy-MM-dd HH:mm:ss.SSS";

    private final static String BASE_FOLDER = "KingDee/File/";

    protected GetKingDeeData kingDee;

    protected ISysConfigService configService;

    private IStorageService storageService;

    protected IUserService userService;

    protected KingDeeFile kingDeeFile;

    @Autowired
    public void setKingDee(GetKingDeeData kingDee) {
        this.kingDee = kingDee;
    }

    @Autowired
    public void setStorageService(IStorageService storageService) {
        this.storageService = storageService;
    }

    @Autowired
    public void setConfigService(ISysConfigService configService) {
        this.configService = configService;
    }

    @Autowired
    public void setSysUserService(IUserService userService) {
        this.userService = userService;
    }

    @Autowired
    public void setKingDeeFile(KingDeeFile kingDeeFile) {
        this.kingDeeFile = kingDeeFile;
    }

    public final void apply(String configKey, HandleData handleData) {
        String condition = null;
        Date configDate = null;
        SysConfig config = null;
        if (StringUtils.isNotBlank(configKey)) {
            config = configService.selectConfigByKeyVal(configKey);
            configDate = incrementConfigTime(config.getConfigValue());
            condition = buildIncrementDateCondition(config.getConfigValue(), config.getRemark());
        }
        AtomicReference<Date> maxTime = new AtomicReference<>(configDate);
        handleData.handleData(condition, maxTime);

        if (config != null)
            updateConfigDate(config.getConfigId(), config.getConfigValue(), configDate, maxTime);
    }

    /**
     * 查询configDate近一个月的数据
     */
    public final void applyLastMonth(String configKey, HandleData handleData) {
        String condition = null;
        Date configDate = null;
        SysConfig config = null;
        if (StringUtils.isNotBlank(configKey)) {
            config = configService.selectConfigByKeyVal(configKey);
            configDate = incrementConfigTime(config.getConfigValue());
            condition = buildIncrementDateConditionLastMonth(config.getConfigValue(), config.getRemark());
        }
        AtomicReference<Date> maxTime = new AtomicReference<>(configDate);
        handleData.handleData(condition, maxTime);

        if (config != null)
            updateConfigDate(config.getConfigId(), config.getConfigValue(), configDate, maxTime);
    }

    /**
     * 查询configDate近5天的的数据
     */
    public final void applyLast5Day(String configKey, HandleData handleData) {
        String condition = null;
        Date configDate = null;
        SysConfig config = null;
        if (StringUtils.isNotBlank(configKey)) {
            config = configService.selectConfigByKeyVal(configKey);
            configDate = incrementConfigTime(config.getConfigValue());
            condition = buildIncrementDateConditionLast5Day(config.getConfigValue(), config.getRemark());
        }
        AtomicReference<Date> maxTime = new AtomicReference<>(configDate);
        handleData.handleData(condition, maxTime);

        if (config != null)
            updateConfigDate(config.getConfigId(), config.getConfigValue(), configDate, maxTime);
    }
    public String buildIncrementDateCondition(String configDate) {
        if (StringUtils.isNotBlank(configDate)) {
            if (configDate.length() == 7) {
                try {
                    Date start = DateUtils.parseDate(configDate.trim() + "-01", "yyyy-MM-dd");
                    String s = DateFormatUtils.format(DateUtils.addMonths(start, 1), "yyyy-MM-dd");
                    String e = DateFormatUtils.format(DateUtils.addMonths(start, 2), "yyyy-MM-dd");
                    return "FDate >= '" + s + "' and FDate < '" + e + "'";
                } catch (java.text.ParseException ex) {
                    ex.printStackTrace();
                }
            }
            return "FModifyDate > '" + configDate + "'";
        }
        return null;
    }

    @SneakyThrows
    public String buildIncrementDateCondition(String configDate, String remark) {
        if (Objects.isNull(remark)) {
            return buildIncrementDateCondition(configDate);
        }
        if (remark.contains("#")) {
            String endTime = remark.substring(0, remark.indexOf("#"));
            return "FModifyDate >= '" + configDate + "' and FModifyDate < '" + endTime + "'";
        }
        if (remark.contains("{")) {
            Date beginDate = incrementConfigTime(configDate);
            String month = StringUtils.substringBetween(remark, "{", "}");
            Date endDate = DateUtils.addMonths(beginDate, Integer.valueOf(month));
            String endTime = DateUtil.format(endDate, "yyyy-MM-dd HH:mm:ss.SSS");
            return "FModifyDate >= '" + configDate + "' and FModifyDate < '" + endTime + "'";
        }
        return buildIncrementDateCondition(configDate);
    }

    /**
     * 查询configDate近一个月的数据
     *
     * @return
     */
    @SneakyThrows
    public String buildIncrementDateConditionLastMonth(String configDate, String remark) {
        Date configVal = DateUtils.parseDate(configDate, updateTimeFormat);
        Date date = DateUtils.addMonths(configVal, -2);
        String oneMonthBefore = com.qijian.common.utils.DateUtils.parseDateToStr(updateTimeFormat, date);
        if (remark.contains("#")) {
            String endTime = remark.substring(0, remark.indexOf("#"));
            return "FModifyDate >= '" + oneMonthBefore + "' and FModifyDate < '" + endTime + "'";
        }
        if (remark.contains("{")) {
            Date beginDate = incrementConfigTime(configDate);
            String month = StringUtils.substringBetween(remark, "{", "}");
            Date endDate = DateUtils.addMonths(beginDate, Integer.valueOf(month));
            String endTime = DateUtil.format(endDate, "yyyy-MM-dd HH:mm:ss.SSS");
            return "FModifyDate >= '" + oneMonthBefore + "' and FModifyDate < '" + endTime + "'";
        }
        return buildIncrementDateCondition(oneMonthBefore);
    }

    /**
     * 查询configDate近5天的数据
     *
     * @return
     */
    @SneakyThrows
    public String buildIncrementDateConditionLast5Day(String configDate, String remark) {
        Date configVal = DateUtils.parseDate(configDate, updateTimeFormat);
        Date date = DateUtils.addDays(configVal, -5);
        String oneMonthBefore = com.qijian.common.utils.DateUtils.parseDateToStr(updateTimeFormat, date);
        if (remark.contains("#")) {
            String endTime = remark.substring(0, remark.indexOf("#"));
            return "FModifyDate >= '" + oneMonthBefore + "' and FModifyDate < '" + endTime + "'";
        }
        if (remark.contains("{")) {
            Date beginDate = incrementConfigTime(configDate);
            String month = StringUtils.substringBetween(remark, "{", "}");
            Date endDate = DateUtils.addMonths(beginDate, Integer.valueOf(month));
            String endTime = DateUtil.format(endDate, "yyyy-MM-dd HH:mm:ss.SSS");
            return "FModifyDate >= '" + oneMonthBefore + "' and FModifyDate < '" + endTime + "'";
        }
        return buildIncrementDateCondition(oneMonthBefore);
    }

    @SneakyThrows
    public Date incrementConfigTime(String configVal) {
        Date configDate = null;
        if (StringUtils.isNotBlank(configVal)) {
            if (configVal.length() == 7)
                configDate = DateUtils.parseDate(configVal + "-01", "yyyy-MM-dd");
            else
                configDate = DateUtils.parseDate(configVal, updateTimeFormat);
        }
        return configDate;
    }

    public void updateConfigDate(final Long configId, String configVal, final Date configDate, AtomicReference<Date> maxDate) {
        if (maxDate == null) return;
        if (configVal.length() == 7) {
            SysConfig config = new SysConfig();
            config.setConfigId(configId);
            config.setConfigValue(DateFormatUtils.format(DateUtils.addMonths(configDate, 1), "yyyy-MM"));
            configService.updateConfig(config);
        } else if (maxDate.get() != null && (configDate == null || maxDate.get().after(configDate))) {
            SysConfig config = new SysConfig();
            config.setConfigId(configId);
            config.setConfigValue(DateFormatUtils.format(maxDate.get(), updateTimeFormat));
            configService.updateConfig(config);
        }
    }


    @FunctionalInterface
    protected interface HandleData {
        void handleData(String condition, AtomicReference<Date> maxTime);
    }


    protected Date composeMaxTime(Date old, Date newest) {
        //筛选最大的时间
        if (old == null || (newest != null && newest.after(old)))
            return newest;
        return old;
    }

    /**
     * 该方法会自动将状态为已废弃的字段给删除,
     * 需要指定废弃的标识字段，并且需要查出来，
     * kingdee删除字段有默认值 'FCancelStatus'
     * <p>
     * 目前删除支持识别的值为[A,0]
     * 非识别的值都会被标记为删除
     *
     * @param data
     * @param maxTime
     * @param clazzArr
     * @return
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    @Transactional(rollbackFor = Exception.class)
    protected Map<Class<?>, List<Object>> process(List<JSONObject> data, AtomicReference<Date> maxTime, Class<?>... clazzArr) {
        if (CollectionUtils.isEmpty(data))
            return new HashMap<>();
        Map<String, Map<KingDeeField, List<Field>>> clazzFieldMap = new HashMap<>(16);

        Map<String, Class<?>> clazzMap = Arrays.stream(clazzArr).collect(Collectors.toMap(Class::getName, a -> a));
        for (Class<?> c : clazzArr) {
            String clazzName = c.getName();
            final List<Field> fields = getAllField(c);
            for (Field field : fields) {
                KingDeeField kf = field.getAnnotation(KingDeeField.class);
                if (kf != null)
                    clazzFieldMap.computeIfAbsent(clazzName, key -> new HashMap<>())
                            .computeIfAbsent(kf, key -> new ArrayList<>()).add(field);
            }
        }
        if (clazzFieldMap.size() == 0)
            return null;
        Map<String, Set<Long>> delIdMap = clazzFieldMap.keySet().stream().collect(Collectors.toMap(a -> a, a -> new HashSet<>()));

        Map<Class<?>, List<Object>> clazzData = clazzFieldMap.keySet().stream().collect(Collectors.toMap(clazzMap::get, a -> new ArrayList<>()));
        clazzFieldMap.forEach((key, fieldMap) -> {
            Class<?> clazz = clazzMap.get(key);
            String deleteField = "FCancelStatus";
            //金蝶表单信息
            KingDeeTable kingDeeTableInfo = clazz.getAnnotation(KingDeeTable.class);
            //
            if (kingDeeTableInfo != null) {
                if (StringUtils.isNotBlank(kingDeeTableInfo.delField()))
                    deleteField = kingDeeTableInfo.delField();
            }

            Map<String, Object> saveMap = new HashMap<>();
            List<Object> saveData = clazzData.get(clazz);
            Field idField = ReflectUtils.findObjectIdField(clazz);
            Asserts.check(idField != null, "无法识别实体主键：" + clazz.getName());
            try {
                for (JSONObject d : data) {
                    Object o = clazz.newInstance();
                    //作废就删除
                    if (StringUtils.isNotBlank(deleteField)
                            && d.containsKey(deleteField)
                            && !"A".equals(d.getString(deleteField))
                            && !"0".equals(d.getString(deleteField))) {
                        KingDeeField kingDeeId = idField.getAnnotation(KingDeeField.class);
                        if (kingDeeId != null) {
                            Long id = d.getLong(kingDeeId.value());
                            delIdMap.get(key).add(id);
                        }
                    } else if (!saveMap.containsKey(String.valueOf(ReflectionUtils.getField(idField, o)))) {
                        fieldMap.forEach((k, fields) -> {
                            fields.forEach(f -> {
                                Set<String> kingDeeFieldSet = d.keySet();
                                ReflectionUtils.makeAccessible(f);
                                if (kingDeeFieldSet.contains(k.value()) && d.get(k.value()) != null) {
                                    if (org.apache.commons.lang3.StringUtils.isNotBlank(k.convert())) {
                                        Map<String, String> collect = Arrays.stream(k.convert().split(","))
                                                .filter(s -> s.contains("=")).collect(Collectors.toMap(
                                                        s -> s.split("=")[0], s -> s.split("=")[1]
                                                ));
                                        setField(f, o, k, collect.get(String.valueOf(d.get(k.value()))));
                                    } else
                                        setField(f, o, k, d.get(k.value()));
                                    if (maxTime != null && "updateTime".equals(f.getName())) {
                                        Date updateTime = (Date) ReflectionUtils.getField(f, o);
                                        maxTime.set(composeMaxTime(maxTime.get(), updateTime));
                                    }
                                } else if (StringUtils.isNotBlank(k.defaultValue()))
                                    setField(f, o, k, k.defaultValue());
                            });
                        });
                        saveMap.put(Objects.nonNull(idField.get(o)) ? String.valueOf(idField.get(o)) : IdUtils.simpleUUID(), o);
                    }
                }
                saveData.addAll(saveMap.values());
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e.getMessage());
            }
        });
        //del
        if (MapUtils.isNotEmpty(delIdMap)) {
            delIdMap.forEach((k, v) -> {
                if (CollectionUtils.isNotEmpty(v)) {
                    Class<?> clazz = clazzMap.get(k);
                    String idFieldName = ReflectUtils.findObjectIdFieldName(clazz);
                    try {
                        Object o = clazz.newInstance();
                        if (o instanceof Model) {
                            Model<?> model = (Model<?>) o;
                            QueryWrapper wrapper = new QueryWrapper<>();
                            wrapper.in(idFieldName, v);
                            model.delete(wrapper);
                        } else {
                            throw new UnsupportedOperationException("无法使用废弃字段自动删除");
                        }
                    } catch (InstantiationException | IllegalAccessException e) {
                        e.printStackTrace();
                        throw new RuntimeException(e.getMessage());
                    }
                }
            });
        }
        return clazzData;

    }


    private void setField(Field f, Object o, KingDeeField k, Object v) {
        if (Objects.nonNull(v)) {
            if (f.getType().isAssignableFrom(Date.class)) {
                String timeStr = String.valueOf(v);
                if (StringUtils.countMatches(timeStr, ".") == 1) {
                    int pointIndex = timeStr.indexOf(".") + 1;
                    timeStr = timeStr.substring(0, pointIndex) + StringUtils.rightPad(timeStr.substring(pointIndex), 3, "0");
                    v = timeStr;
                }
            }
            ReflectionUtils.setField(f, o, TypeUtils.cast(v, f.getType(), ParserConfig.getGlobalInstance()));
        } else if (k != null && StringUtils.isNotBlank(k.defaultValue())) {
            ReflectionUtils.setField(f, o, TypeUtils.cast(k.defaultValue(), f.getType(), ParserConfig.getGlobalInstance()));
        }
    }

    protected <T> List<T> listObj2Bean(List<Object> data, Class<T> bean) {
        if (CollectionUtils.isEmpty(data))
            return new ArrayList<>();
        return data.stream().map(o -> TypeUtils.castToJavaBean(o, bean)).collect(Collectors.toList());
    }

    protected String getItemAttr(JSONObject p) {
        return buildItemAttr(
                p.getString("FAUXPROPID.FF100001.fdatavalue"),
                p.getString("FAUXPROPID.FF100002.fdatavalue"),
                p.getString("FAUXPROPID.FF100003"),
                p.getString("FAUXPROPID.FF100004"),
                p.getString("FAUXPROPID.FF100005"),
                p.getString("FAUXPROPID.FF100006.fdatavalue"),
                p.getString("FAUXPROPID.FF100007"),
                p.getString("FAUXPROPID.FF100008"),
                p.getString("FAUXPROPID.FF100009"),
                p.getString("FAUXPROPID.FF100013")
        );
    }


    protected String buildItemAttr(String caizhi, String hou, String kuan, String chang, String pinpai, String guigexinghao,
                                   String kucunxiangmu, String fushi, String kucunbeizhu, String pohaozhuangpei) {
        JSONObject attr = new JSONObject();
        if (StringUtils.isNotBlank(caizhi))
            attr.put("材质", caizhi);
        if (StringUtils.isNotBlank(hou))
            attr.put("厚", hou);
        if (StringUtils.isNotBlank(kuan))
            attr.put("宽", kuan);
        if (StringUtils.isNotBlank(chang))
            attr.put("长", chang);
        if (StringUtils.isNotBlank(pinpai))
            attr.put("品牌", pinpai);
        if (StringUtils.isNotBlank(guigexinghao))
            attr.put("规格型号", guigexinghao);
        if (StringUtils.isNotBlank(kucunxiangmu))
            attr.put("库存项目", kucunxiangmu);
        if (StringUtils.isNotBlank(fushi))
            attr.put("复试", fushi);
        if (StringUtils.isNotBlank(kucunbeizhu))
            attr.put("库存备注", kucunbeizhu);
        if (StringUtils.isNotBlank(pohaozhuangpei))
            attr.put("PO号装配", pohaozhuangpei);
        return attr.toJSONString();
    }

    private JSONObject buildItemAttrValue(String key, String val) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name", key);
        jsonObject.put("value", val);
        return jsonObject;
    }

    protected static void resetId(List<JSONObject> data, String suffix, String... key) {
        Asserts.check(key.length > 0, "无key,无效调用");
        //多表合一，会主键冲突，为解决这个问题需要在id后面拼上固定的代表数字
        data.forEach(d -> {
            for (String s : key) {
                Asserts.check(d.containsKey(s), "不存在key:" + s);
                Long newValue = Long.valueOf(d.getLong(s) + suffix);
                d.put(s, newValue);
            }

        });
    }


    /**
     * 下载金蝶附件
     *
     * @param clazz
     * @param billType
     * @param ids
     */
    void downloadKingDeeFileByInfo(Class<?> clazz, String billType, List<Long> ids) {
        if (ids.size() > 0) {
            //同时拉取金蝶所有附件信息
            List<KingDeeFile.KingDeeFileBean> kingDeeFileList = kingDeeFile.getKingDeeFile(billType, ids);
            if (kingDeeFileList.size() == 0) {
                return;
            }
            storageService.removeAndFileByClassIds(ids);
//            //过滤掉已存在的文件
//            List<Long> storageIds = storageService.storageIdToFilter(clazz.getSimpleName(), ids);
//            List<KingDeeFile.KingDeeFileBean> files = kingDeeFileList.stream().filter(f -> !storageIds.contains(f.getId())).collect(Collectors.toList());
//            if (files.size() == 0) {
//                return;
//            }
            this.downloadKingDeeFileNew(clazz, kingDeeFileList);
        }
    }

    /**
     * 不会update,如需过滤已存在的数据需要提前过滤
     *
     * @param clazz
     * @param kingDeeFiles
     */
    @Transactional(rollbackFor = Exception.class)
    public void downloadKingDeeFile(Class<?> clazz, List<KingDeeFile.KingDeeFileBean> kingDeeFiles) {
        List<Storage> storages = new ArrayList<>();
        kingDeeFiles.forEach(f -> {
            String folder = f.getBillType();
            String s = kingDee.downloadPath(f.getFileId());
            Storage storage = HttpFileUtil.getFileByUrlToAliYun(s, BASE_FOLDER + folder, f.getFileName());
            storage.setStorageId(f.getId());
            storage.setClassTable(clazz.getSimpleName());
            storage.setClassId(f.getEntryId());
            storages.add(storage);
        });
        storageService.saveBatch(storages);
    }


    /**
     * 不会update,如需过滤已存在的数据需要提前过滤
     *
     * @param clazz
     * @param kingDeeFiles
     */
    @Transactional(rollbackFor = Exception.class)
    public void downloadKingDeeFileNew(Class<?> clazz, List<KingDeeFile.KingDeeFileBean> kingDeeFiles) {
        List<Storage> storages = new ArrayList<>();
        kingDeeFiles.forEach(f -> {
            String folder = f.getBillType();
            String s = kingDee.downloadPath(f.getFileId());
            Storage storage = HttpFileUtil.getFileByUrlToAliYun(s, BASE_FOLDER + folder, f.getFileName());
            //ID可能会重复（其他模块），所以采用自增策略
            //storage.setStorageId(f.getId());
            storage.setClassTable(clazz.getSimpleName());
            storage.setClassId(f.getEntryId());
            storages.add(storage);
        });
        storageService.saveBatch(storages);
    }
}
